package seatiger.tomcat.catalina.connector.http;

import seatiger.tomcat.catalina.*;
import seatiger.tomcat.catalina.connector.HttpConnector;
import seatiger.tomcat.catalina.util.LifecycleSupport;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.io.EOFException;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 处理器线程
 */
@Deprecated
public class HttpProcessor implements Runnable, Lifecycle {

    private HttpRequestImpl request;

    private HttpResponseImpl response;

    private int serverPort = 0;

    private HttpConnector connector;

    private Socket socket;

    private Thread thread;

    private String threadName;

    //当有socket连接进入的时候，连接变得可用
    private boolean available = false;

    private volatile boolean stopped = false;

    private boolean started = false;

    private LifecycleSupport lifecycle = new LifecycleSupport(this);

    private boolean keepAlive = false;

    /**
     * 请求行
     */
    private HttpRequestLine requestLine = new HttpRequestLine();

    public HttpProcessor(HttpConnector connector,int id){
        this.connector = connector;
        this.request = (HttpRequestImpl) connector.createRequest();
        this.response = (HttpResponseImpl) connector.createResponse();
        this.threadName =
                "HttpProcessor[" + connector.getPort() + "][" + id + "]";
    }

    /**
     * 解析和客户端连接的socket请求
     */
    public void process() {
        SocketInputStream input = null;
        Boolean ok = true;
        boolean finishResponse = true;
        OutputStream output = null;
        //获取流
        try {
            input = new SocketInputStream(socket.getInputStream(),2048);
        } catch (Exception e) {
            log("process.create", e);
            ok = false;
        }

        keepAlive = true;
        while (!stopped && ok && keepAlive){
            finishResponse = true;
            //准备数据
            try {
                output = socket.getOutputStream();
                request.setStream(input);
                request.setResponse(response);
                output = socket.getOutputStream();
                response.setStream(output);
                response.setRequest(request);
            }catch (Exception e) {
                log("process.create", e);
                ok = false;
            }
            //解析数据
            try {
                if (ok) {
                    parseConnection(socket);
                    parseRequest(input);
                    parseHeaders(input);
                }
            }catch (EOFException e){
                ok = false;
                finishResponse = false;
            } catch (ServletException e) {
                ok = false;
                try {
                    ((HttpServletResponse) response.getResponse())
                            .sendError(HttpServletResponse.SC_BAD_REQUEST);
                } catch (Exception f) {
                }
            } catch (Exception e) {
                try {
                    log("process.parse", e);
                    ((HttpServletResponse) response.getResponse()).sendError
                            (HttpServletResponse.SC_BAD_REQUEST);
                } catch (Exception f) {
                }
                ok = false;
            }

            //执行数据
            try {
                if (ok){
                    connector.getContainer().invoke(request,response);
                }
            } catch (ServletException e) {
                log("process.invoke", e);
                try {
                    ((HttpServletResponse) response.getResponse()).sendError
                            (HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                } catch (Exception f) {
                }
                ok = false;
            } catch (Throwable e) {
                log("process.invoke", e);
                try {
                    ((HttpServletResponse) response.getResponse()).sendError
                            (HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                } catch (Exception f) {
                }
                ok = false;
            }

            if (finishResponse){
                try {
                    response.finishResponse();
                } catch (IOException e) {
                    ok = false;
                }catch (Throwable e) {
                    log("process.invoke", e);
                    ok = false;
                }

                try {
                    request.finishRequest();
                } catch (IOException e) {
                    ok = false;
                }catch (Throwable e) {
                    log("process.invoke", e);
                    ok = false;
                }
                try {
                    if (output != null)
                        output.flush();
                } catch (IOException e) {
                    ok = false;
                }
            }

            if ( "close".equals(response.getHeader("Connection")) ) {
                keepAlive = false;
            }
            request.recycle();
            response.recycle();
        }

        try {
            socket.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    private void parseHeaders(SocketInputStream input) throws IOException, ServletException{
        while (true){
            HttpHeader header = new HttpHeader();
            input.readHeader(header);
            if (!checkHeader(header)){
                return;
            }
            String headName = header.getHeadName();
            String headValue = header.getHeadValue();
            Arrays.stream(headValue.split(",")).filter(v -> !v.equals(" ")).forEach(v -> request.addHeader(headName,v));
            switch (headName){
                case DefaultHeaders.AUTHORIZATION_NAME: request.setAuthorization(headValue); break;
                case DefaultHeaders.CONTENT_LENGTH_NAME: request.setContentLength(Integer.valueOf(headValue)); break;
                case DefaultHeaders.CONTENT_TYPE_NAME: request.setContentType(headValue); break;
                case DefaultHeaders.HOST: parseHost(request,headValue); break;
                case DefaultHeaders.COOKIE: parseCookie(request,headValue); break;
            }
        }
    }

    /**
     * 解析cookie
     * @param headValue
     */
    private void parseCookie(HttpRequestImpl request,String headValue) {
        List<Cookie> cookies = parseCookieHeader(headValue);
        cookies.stream().filter(cookie -> Globals.SESSION_COOKIE_NAME.equals(cookie.getName()))
                .forEach(cookie -> {
                    if (request.getRequestedSessionId() == null){
                        request.setRequestedSessionId(cookie.getValue());
                    }
                    request.addCookie(cookie);
                });
    }

    private List<Cookie> parseCookieHeader(String headValue){
        if (headValue == null || headValue.length() == 0){
            return new ArrayList<>(0);
        }
        String[] heads = headValue.split(";");
        List<Cookie> cookies = new ArrayList<>(1);
        for (String head: heads){
            String[] split = head.split("=");
            cookies.add(new Cookie(split[0],split[1]));
        }
        return cookies;
    }

    private void parseHost(HttpRequestImpl request,String headName){
        String[] split = headName.split(":");
        if (split.length > 1){
            request.setServerName(split[0]);
            request.setServerPort(Integer.valueOf(split[1]));
        }else if(split.length == 1){
            request.setServerName(headName);
            request.setServerPort(80);
        }
    }

    private boolean checkHeader(HttpHeader header) throws ServletException{
        if (header.nameEnd == 0) {
            if (header.valueEnd == 0) {
                return false;
            } else {
                throw new ServletException
                        ("httpProcessor.parseHeaders.colon");
            }
        }
        header.setHeadName(new String(header.name, 0, header.nameEnd));
        header.setHeadValue(new String(header.value,0,header.valueEnd));
        return true;
    }


    /**
     * 解析请求
     * @param input
     */
    private void parseRequest(SocketInputStream input) throws IOException, ServletException {
        input.readRequestLine(requestLine);
        checkRequestLine(requestLine);
        String methodName = requestLine.getMethodName();
        String uriName = requestLine.getUriName();
        String protocolName = requestLine.getProtocolName();
        request.setMethod(methodName);
        int index = uriName.indexOf("?");
        if (index > -1){
            request.setQueryString(uriName.substring(index+1));
            request.setRequestURI(uriName.substring(0,index));
        }else {
            request.setQueryString(null);
            request.setRequestURI(uriName);
        }
        request.setProtocol(protocolName);
        request.setScheme(connector.getScheme());
    }

    /**
     * 校验请求行是否合法
     * @param requestLine
     */
    private void checkRequestLine(HttpRequestLine requestLine) throws ServletException {
        if (requestLine.methodEnd < 1){
            throw new ServletException("httpProcessor.parseRequest.method");
        }
        if (requestLine.uriEnd < 1){
            throw new ServletException("httpProcessor.parseRequest.uri");
        }
        if (requestLine.protocolEnd < 1){
            throw new ServletException("httpProcessor.parseRequest.rotocol");
        }
        requestLine.setMethodName(new String(requestLine.method, 0, requestLine.methodEnd));
        requestLine.setUriName(new String(requestLine.uri, 0,requestLine.uriEnd));
        requestLine.setProtocolName(new String(requestLine.protocol, 0,requestLine.protocolEnd));
    }

    /**
     * 解析一个连接
     * @param socket
     */
    private void parseConnection(Socket socket){
        request.setInet(socket.getInetAddress());
        request.setServerPort(serverPort);
        request.setSocket(socket);
    }

    @Override
    public void run() {
        while (!stopped){
            //阻塞
            await();
            System.out.println(Thread.currentThread().getName() + "work!!!!");
            System.out.println(socket);
            if (socket == null){
                continue;
            }
            try {
                process();
            }catch (Throwable t){
                t.printStackTrace();
            }
            connector.recycle(this);
        }


    }

    private synchronized void await() {
        while (!available){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        available = false;
    }

    public synchronized void assign(Socket socket) {
        this.available = true;
        this.socket = socket;
        notify();
    }

    public void threadStart(){
        thread = new Thread(this,threadName);
        thread.setDaemon(true);
        thread.start();
    }

    @Override
    public void stop() throws LifecycleException {
        if (!started){
            throw new LifecycleException("httpProcessor.notStarted");
        }
        lifecycle.fireLifecycleEvent(STOP_EVENT, null);
        started = false;
        threadStop();
    }

    private void threadStop() {
        stopped = true;
        assign(null);
        thread = null;
    }


    @Override
    public void start() throws LifecycleException {
        if (started)
            throw new LifecycleException("httpProcessor.alreadyStarted");
        lifecycle.fireLifecycleEvent(START_EVENT, null);
        started = true;
        threadStart();
    }

    @Override
    public void addLifecycleListener(LifecycleListener listener) {
        lifecycle.addLifecycleListener(listener);
    }

    @Override
    public List<LifecycleListener> findLifecycleListeners() {
        return lifecycle.findLifecycleListeners();
    }

    @Override
    public void removeLifecycleListener(LifecycleListener listener) {
        lifecycle.removeLifecycleListener(listener);
    }

    /**
     * 日志打印
     * @param message
     * @param throwable
     */
    private void log(String message, Throwable throwable) {
        Logger logger = connector.getContainer().getLogger();
        if (logger != null){
            logger.log(threadName + " " + message, throwable);
        }
    }
}
